李守中

Btrfs 相关

Table of Contents

1. 基础知识

1.1. 概念解释

extent

它是用于构成 chunk 的,由数个连续 block 组成的逻辑空间。它的语义通常是「数个 block 组成的连续的逻辑空间」。

extent 是存储数据的最小的逻辑单位 (存储数据的最小的物理单位是 block),只能作为整体被不同对象共享 (比如 reflink),不能存储不同对象的数据 (比如一个 extent 不能存储两个文件的数据)。extent 的大小由 btrfs 决定,最小为 4K,一旦确定就不可被更改。

比如,有一个 1M 大小的文件使用了 1 个 1M 大小的 extent,那么再向这个文件写入 512K 的新数据之后,这个文件最终会使用 1M 大小的 extent 和 512K 大小的 extent 一共两个 extent。只有当 1M 大小的 extent 中的数据被写往别处时,这个旧的 1M 大小的 extent 才被释放。如果修改了 1M extent 中的一部分数据,可能导致这个 1M extent 分裂成三份 (修改位置之前的、修改位置的和修改位置之后的数据);或者 btrfs 读出整个 extent 的数据,修改完后将这一整个 extent 覆写会原位置。总之,extent 的管理策略极为复杂,用户不需要关心。

如果某个文件被 btrfs 压缩后存储在硬盘上,那么该文件被压缩的 extent 的最大大小是 128K。这是因为 btrfs 在读取被压缩的数据中的某个 block 时,必须先解压完整的 extent,设置过大的 extent 会严重影响被压缩的文件的读写性能。为了避免这个问题,btrfs 取了一个折中的 128K 大小作为被压缩的数据的最大的 extent 的大小。

chunk (block group)

btrfs 中有三种 chunk: data, metadata 和 system。chunk 是由数个 extent 组成的一个逻辑空间。chunk 和 block group 有时可以互换使用。它的语义通常是「被 btrfs 动态分配,用于存储 data, metadata 或 system 数据的空间」。extent 最终会被写入到 chunk 中。

这三类 chunk 的大小只能在创建文件系统时修改。通常,data chunk 的大小总是 1G。在文件系统小于 50G 时,metadata chunk 大小是 256M;在文件系统大小为 50G 及以上时,metadata chunk 大小是 1G。文档里只说 system chunk 的大小是数 M,经测试,在 4*8T raid10 的文件系统中,system chunk 大小是 32M。

1.2. 文件自动修复 (self healing)

btrfs 与 zfs 类似,也支持异常数据自动修复。与 zfs 相同,btrfs 文件系统中,只有在数据有副本,比如使用 dup, raid10, raid1c3 等存储模式;或者使用 raid5/6 这种可以恢复文件的存储模式时,btrfs 才会自动修复异常的数据。

如果数据没有副本,或者没有使用可以修复数据的存储模式,则 btrfs 与 zfs 相同,只能检测到数据异常,但无法修复。

2. 使用技巧

2.1. 挂载参数

btrfs 的挂载参数按影响范围可以划分为针对全局的参数和针对子卷 subvolume 的参数。

btrfs 的全局挂载参数由首个被挂载的子卷决定,如果有些必须的挂载参数没有被用户指定,那就使用默认值。并且,后续挂载其他子卷时使用的全局参数全部不生效。

此外,需要注意:

  • 不能单独为某个子卷关闭 CoW 特性,因为 nodatacow 是个全局挂载参数,但可以通过 chattr +C <path> 命令对某个文件或目录关闭 CoW 特性 (作用于目录时,目录内已有文件不受影响,新文件关闭 CoW 特性)

2.1.1. compress 和 compress-force

compress 挂载选项表示,btrfs 会压缩要被压缩的文件的前一部分数据 (排在前面的数个 extent),如果文件的前一部分数据压缩效果好,那么这个文件中,每一个 extent 都会被 btrfs 压缩一遍;如果文件的前一部分数据压缩效果不好,那么整个文件都不会被压缩,直接存入硬盘。

compress-force 挂载选项表示,btrfs 不会判断文件是否可压缩,而是直接压缩文件的每一个 extent。

这些被压缩的 extent 中,压缩效果好才会被保留,压缩效果不好的 extent 会丢弃被压缩好的数据,直接使用压缩前的数据。

注: 我没找见这个衡量压缩效果好坏的量化指标。

就个人使用情况与经验而言,使用 compress 即可,不推荐使用 compress-force 作为挂载选项。特别是存储大量媒体数据的使用场景。

不推荐的理由是: compress-force 会导致不可压缩文件的碎片化程度急剧增加。

给一个例子,这是一个视频文件分别被 compress-force 和 compress 挂载参数处理后的效果:

Processed 1 file, 14282 regular extents (14282 refs), 0 inline.
Type       Perc     Disk Usage   Uncompressed Referenced
TOTAL       99%      6.9G         6.9G         6.9G
none       100%      6.9G         6.9G         6.9G
zstd        35%      5.9M          16M          16M

Processed 1 file, 311 regular extents (311 refs), 0 inline.
Type       Perc     Disk Usage   Uncompressed Referenced
TOTAL      100%      6.9G         6.9G         6.9G
none       100%      6.9G         6.9G         6.9G

从例子中可以看到,压缩视频文件只能节省约 10M 的空间,但是文件碎片化程度有了指数级的上升。

这是因为,前面提到,被压缩后的 extent 的最大大小是 128K,而 compress-force 选项会导致所有的数据都被压缩一遍,这就意味着原本可以使用较大 extent 的文件必须被更加地细分,将数据放入小的 extent 中,才能满足压缩算法的需要。

而这种行为恰恰导致了文件的碎片化。特别是对于媒体文件来说,文件越大,碎片化越严重。

如果想要把受 compress-force 压缩模式影响的数据重新使用 compress 压缩模式压缩一遍,以减小文件的碎片化程度,可以使用通过 defragment 来变更压缩算法的方法来让所有文件以 compress 压缩模式被重新压缩。

2.1.2. datacow/nodatacow 和 datasum/nodatasum

datacownodatacow 的含义为是否为该挂载点内的数据启用 CoW 特性。

datasumnodatasum 的含义为是否计算并存储该挂载点内的 extent 的 checksum。

nodatacow 和 nodatasum 启用后,都禁用了文件系统压缩功能。

提这两类挂载选项是因为: datasum 与 datacow 互相隐含;nodatasum 与 nodatacow 互相隐含。

也就是说,如果不启用 CoW 特性,那么 extent 的 checksum 也不会被计算并存储,反之亦然;如果启用 CoW 特性,那么 extent 的 checksum 会被计算并存储,反之亦然。

2.2. 变更压缩算法 (通过 defragment)

修改子卷使用的压缩算法之后,可以使用 btrfs filesystem defragment -r -v -c<compress-alg> <mount-point> 开始对子卷的挂载点进行碎片整理, -c 参数会让所有文件在碎片整理过程中被新的压缩算法重新压缩。

注: -r 递归目录,但不会递归地进入挂载点或子卷; -v 输出详细过程; -c 指定压缩算法。

但是需要注意,在对建有快照的子卷上进行碎片整理可能会触发 CoW 导致没有变更的 extent 也被重新分配了空间,而由于快照的存在,旧 extent 占有的空间也不会被释放,从而导致空间使用率增加。

2.3. 碎片整理的 defragment 操作

使用 CoW 特性的文件系统会比非 CoW 文件系统更容易产生碎片。

因为,在使用 CoW 特性时,当一个数据被从硬盘上读出,并写回硬盘,CoW 文件系统的每一次都会将这些数据写入到硬盘上的另一个位置,而不是数据原先在硬盘上所处的位置。

所以,使用一段时间后,extent 可能被分配得过于离散,或 extent 被打散到较小的空间中,这都会增加硬盘寻道消耗的时间。

碎片整理 btrfs filesystem defragment [options] <file>|<dir> [<file>|<dir>…] 可以将被碎片化存储 (分布在多个、多处的 extent 中) 的数据,重新读到内存中,然后尽可能地将他们一起写入到硬盘上。

defragment 优化的是 extent 的分布和分配,它将被打散的 extent 重新组合成一个较大的 extent,并使文件的所有 extent 在硬盘上的位置尽可能地近。所以 defragment 会让数据所在的位置更加连续,从而减少硬盘寻道消耗的时间。

这个命令有一个重要的参数 -t (默认值 32M),它向碎片整理操作传递了一个描述 extent 大小的值。表示,大小低于该值的 extent 会被碎片整理程序考虑与该文件的其他 extent 合并并重新写入。多数时候,保持默认即可。

碎片整理之后,可用空间有可能不连续,此时可以进一步用 balance 重新排列数据,从而让可用空间尽量连续。

2.4. 让数据均匀分布的 balance 操作

btrfs balance 是一种获取 btrfs 文件系统上的所有数据和元数据,然后通过分配器算法进行传递,最后将其重新写入磁盘上的不同位置的操作。它最初为多设备文件系统设计,目的是让数据在在设备间更均匀地分布。所以这个操作在将新设备添加到几乎已满的文件系统时尤其有用。

balance 优化的是 chunk 的分布和分配,它将未被充分使用的 chunk 中的数据合并在有空余空间的 chunk 中,并回收腾出的 chunk,并让 chunk 的分布尽量连续。所以在 chunk 数量不足时,它可以回收一些未被充分使用的 chunk,从而让 btrfs 拥有更多可分配的空间;也可以在 chunk 被分配得过于离散时,重新调整 chunk 的分布,使其更加连续以减少硬盘寻道消耗的时间。

这个功能还有一些其它作用:

  • 可以用于重建 raid。在一个使用了 raid1 的 btrfs 上,如果有一个新硬盘替换了已经损坏的硬盘,那么在这个新硬盘被加入文件系统之后,新硬盘中没有数据。balance 操作会让 btrfs 重建应该存在于新硬盘上的数据
  • 可以改变 data 或者 metadata 的存储模式,比如让将文件 data 部分在硬盘上存储两份 btrfs balance start -dconvert=dup <path> (metadata 的默认模式就是 dup ,即默认在硬盘上存两份相同的 metadata)

在 btrfs 3.14 之后,有时需要使用 balance 操作来修复由于 chunk 用尽导致无法给 metadata 分配 chunk 从而出现文件系统已满的问题。这里要注意只 balance data chunk,不要动 metadata chunk,因为元数据越是集中存放,将来就越可能需要分配新的 chunk,就越有可能遇到没 chunk 可以分配给 data 的情况。

有一个指标可以用来参考是否需要 balance:

# btrfs filesystem df /mnt/mp_storage0
Data, RAID10: total=4.38TiB, used=4.37TiB  # total 指已分配的空间,used 指当前用了多少已分配的空间
System, RAID10: total=16.00MiB, used=464.00KiB
Metadata, RAID10: total=7.50GiB, used=7.01GiB
GlobalReserve, single: total=512.00MiB, used=0.00B

used/total 如果得出的百分比过低,则有可能出现 chunk 中有大量未使用空间的情况。而这种情况下 defragment 操作没有用,defragment 只影响文件的连续性,不影响 chunk 分配。所以此时最好使用 balance 把未使用的 chunk 空间回收一下。

3. 实用工具 (第三方)

btrfs-compsize

btrfs 官方没有提供查看压缩率情况的工具,这个工具可以查看 btrfs 子卷挂载点的压缩情况。

btrfs-heatmap

可以查看 block group 的分布和使用情况。

snapper

由 SUSE 开发,可以实现快照的自动管理。可配置参数丰富,以命令行为主要使用方式。



Last Update: 2024-04-19 Fri 18:35

Generated by: Emacs 28.2 (Org mode 9.5.5)   Contact: [email protected]

若正文中无特殊说明,本站内容遵循: 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议